<template>
  <div ref="outerWrapper" :class="wrapperClasses">
    <div v-if="isHorizontal" :class="`${prefix}-horizontal`">
      <div :style="{ right: `${anotherOffset}%` }" class="left-pane" :class="paneClasses">
        <slot name="left" />
      </div>
      <div :class="`${prefix}-trigger-con`" :style="{ left: `${offset}%` }" @mousedown="handleMousedown">
        <slot name="trigger">
          <trigger mode="vertical" />
        </slot>
      </div>
      <div :style="{ left: `${offset}%` }" class="right-pane" :class="paneClasses">
        <slot name="right" />
      </div>
    </div>
    <div v-else :class="`split-vertical`">
      <div :style="{ bottom: `${anotherOffset}%` }" class="top-pane" :class="paneClasses">
        <slot name="top" />
      </div>
      <div :class="`${prefix}-trigger-con`" :style="{ top: `${offset}%` }" @mousedown="handleMousedown">
        <slot name="trigger">
          <trigger mode="horizontal" />
        </slot>
      </div>
      <div :style="{ top: `${offset}%` }" class="bottom-pane" :class="paneClasses">
        <slot name="bottom" />
      </div>
    </div>
  </div>
</template>
<script>
import { nextTick } from 'vue'
import { oneOf, on, off } from './utils'
import Trigger from './trigger.vue'

export default {
  name: 'Split',
  components: { Trigger },
  emits: ['update:modelValue', 'on-move-start', 'on-moving', 'on-move-end'],
  props: {
    modelValue: {
      type: [Number, String],
      default: 0.5
    },
    mode: {
      validator(value) {
        return oneOf(value, ['horizontal', 'vertical'])
      },
      default: 'horizontal'
    },
    min: {
      type: [Number, String],
      default: '40px'
    },
    max: {
      type: [Number, String],
      default: '40px'
    }
  },
  /**
   * Events
   * @on-move-start
   * @on-moving 返回值：事件对象，但是在事件对象中加入了两个参数：atMin(当前是否在最小值处), atMax(当前是否在最大值处)
   * @on-move-end
   */
  data() {
    return {
      prefix: 'cy-split',
      offset: 0,
      oldOffset: 0,
      isMoving: false,
      computedMin: 0,
      computedMax: 0,
      currentValue: 0.5
    }
  },
  computed: {
    wrapperClasses() {
      return [
        `${this.prefix}-wrapper`,
        this.isMoving ? 'no-select' : ''
      ]
    },
    paneClasses() {
      return [
        `${this.prefix}-pane`,
        {
          [`${this.prefix}-pane-moving`]: this.isMoving
        }
      ]
    },
    isHorizontal() {
      return this.mode === 'horizontal'
    },
    anotherOffset() {
      return 100 - this.offset
    },
    valueIsPx() {
      return typeof this.modelValue === 'string'
    },
    offsetSize() {
      return this.isHorizontal ? 'offsetWidth' : 'offsetHeight'
    }
  },
  methods: {
    px2percent(numerator, denominator) {
      return parseFloat(numerator) / parseFloat(denominator)
    },
    getComputedThresholdValue(type) {
      let size = this.$refs.outerWrapper[this.offsetSize]
      if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type]
      else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type]
    },
    getMin(value1, value2) {
      if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`
      else return Math.min(value1, value2)
    },
    getMax(value1, value2) {
      if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`
      else return Math.max(value1, value2)
    },
    getAnotherOffset(value) {
      let res = 0
      if (this.valueIsPx) res = `${this.$refs.outerWrapper[this.offsetSize] - parseFloat(value)}px`
      else res = 1 - value
      return res
    },
    handleMove(e) {
      let pageOffset = this.isHorizontal ? e.pageX : e.pageY
      let offset = pageOffset - this.initOffset
      let outerWidth = this.$refs.outerWrapper[this.offsetSize]
      let value = this.valueIsPx ? `${parseFloat(this.oldOffset) + offset}px` : (this.px2percent(outerWidth * this.oldOffset + offset, outerWidth))
      let anotherValue = this.getAnotherOffset(value)
      if (parseFloat(value) <= parseFloat(this.computedMin)) value = this.getMax(value, this.computedMin)
      if (parseFloat(anotherValue) <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax))
      e.atMin = this.modelValue === this.computedMin
      e.atMax = this.valueIsPx ? this.getAnotherOffset(this.modelValue) === this.computedMax : this.getAnotherOffset(this.modelValue).toFixed(5) === this.computedMax.toFixed(5)
      this.$emit('update:modelValue', value)
      this.$emit('on-moving', e)
    },
    handleUp() {
      this.isMoving = false
      off(document, 'mousemove', this.handleMove)
      off(document, 'mouseup', this.handleUp)
      this.$emit('on-move-end')
    },
    handleMousedown(e) {
      this.initOffset = this.isHorizontal ? e.pageX : e.pageY
      this.oldOffset = this.modelValue
      this.isMoving = true
      on(document, 'mousemove', this.handleMove)
      on(document, 'mouseup', this.handleUp)
      this.$emit('on-move-start')
    },
    computeOffset() {
      nextTick(() => {
        this.computedMin = this.getComputedThresholdValue('min')
        this.computedMax = this.getComputedThresholdValue('max')
       
        this.offset = (this.valueIsPx ? this.px2percent(this.modelValue, this.$refs.outerWrapper[this.offsetSize]) : this.modelValue) * 10000 / 100
      })
    }
  },
  watch: {
    modelValue(val) {
      if (val !== this.currentValue) {
        this.currentValue = val
        this.computeOffset()
      }
    }
  },
  mounted() {
    nextTick(() => {
      this.computeOffset()
    })

    on(window, 'resize', this.computeOffset)
  },
  beforeUnmount() {
    off(window, 'resize', this.computeOffset)
  }
}
</script>
<style lang="scss">
.cy-split {
  &-wrapper {
    position: relative;
    width: 100%;
    height: 100%;
  }

  &-pane {
    position: absolute;

    &.left-pane,
    &.right-pane {
      top: 0;
      bottom: 0;
    }

    &.left-pane {
      left: 0;
    }

    &.right-pane {
      right: 0;
      padding-left: 10px;
    }

    &.top-pane,
    &.bottom-pane {
      left: 0;
      right: 0;
    }

    &.top-pane {
      top: 0;
    }

    &.bottom-pane {
      bottom: 0;
    }

    &-moving {
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
  }

  &-trigger {
    border: 1px solid var(--el-border-color);

    &-con {
      position: absolute;
      transform: translate(-50%, -50%);
      z-index: 10;
    }

    &-bar-con {
      position: absolute;
      overflow: hidden;

      &.vertical {
        left: 2px;
        top: 50%;
        height: 8px;
        transform: translate(0, -50%);
      }

      &.horizontal {
        left: 50%;
        top: 2px;
        width: 8px;
        transform: translate(-50%, 0);
      }
    }

    &-vertical {
      width: 6px;
      height: 100%;
      background: #f8f8f9;
      border-top: none;
      border-bottom: none;
      cursor: col-resize;

      .cy-split-trigger-bar {
        width: 4px;
        height: 1px;
        background: rgba(23, 35, 61, 0.25);
        float: left;
        margin-top: 3px;
      }
    }

    &-horizontal {
      height: 6px;
      width: 100%;
      background: #f8f8f9;
      border-left: none;
      border-right: none;
      cursor: row-resize;

      .cy-split-trigger-bar {
        height: 4px;
        width: 1px;
        background: rgba(23, 35, 61, 0.25);
        float: left;
        margin-right: 3px;
      }
    }
  }

  &-horizontal {
    >.cy-split-trigger-con {
      top: 50%;
      height: 100%;
      width: 0;
    }
  }

  &-vertical {
    >.cy-split-trigger-con {
      left: 50%;
      height: 0;
      width: 100%;
    }
  }

  .no-select {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
  }
}</style>
